package com.gitee.tje.waving.aop;

import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.objenesis.ObjenesisHelper;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * aop扫描器
 *
 * @author taojiaen
 * @date 2018-03-06 20:17
 */
public class AopCreator {
    private final Collection<Advisor> advisors;

    public AopCreator(Collection<Advisor> advisors) {
        this.advisors = advisors;
    }

    public Object create(Object target) {
        if (target == null) {
            throw new NullPointerException("target is null");
        }

        Map<Method, List<MethodHook>> methodListMap = getMethodHookListMap(target);
        if (methodListMap.size() == 0) {
            return target;
        }

        return createCglibWrapper(new TargetSource(target, methodListMap));
    }

    private Object createCglibWrapper(TargetSource targetSource) {
        CglibMethodInterceptor invoker = new CglibMethodInterceptor(targetSource);
        Enhancer enhancer = new Enhancer();
        enhancer.setUseCache(false);
        enhancer.setSuperclass(ClassUtils.getUserClass(targetSource.target));
        enhancer.setCallbackType(invoker.getClass());
        Class<?> proxyClass = enhancer.createClass();
        Enhancer.registerStaticCallbacks(proxyClass, new Callback[]{invoker});
        return ObjenesisHelper.newInstance(proxyClass);
    }

    private class CglibMethodInterceptor implements MethodInterceptor {
        private final MethodInvoker methodInvoker;

        private CglibMethodInterceptor(TargetSource targetSource) {
            this.methodInvoker = new MethodInvoker(targetSource);
        }

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            return methodInvoker.invoke(method, objects);
        }
    }

    private Map<Method, MethodHook> getAllMethodHook(Advisor advisor, Object target) {
        Map<Method, MethodHook> methodMethodHookMap = new HashMap<>(32);

        if (advisor instanceof PointCutAdvisor) {
            boolean isAccepted = ((PointCutAdvisor) advisor).getPointCut().isAllValid(target);

            ReflectionUtils.doWithLocalMethods(
                    target.getClass()
                    , method -> {
                        if (method.getModifiers() == 9) return;

                        if (isAccepted || ((PointCutAdvisor) advisor).getPointCut().isMethodValid(method)) {
                            if (advisor.getAdvice() instanceof MethodHook) {
                                methodMethodHookMap.put(method, (MethodHook) advisor.getAdvice());
                            }
                        }
                    });
        }

        return methodMethodHookMap;
    }


    private Map<Method, List<MethodHook>> getMethodHookListMap(Object target) {
        Map<Method, List<MethodHook>> methodListMap = new HashMap<>(32);
        for (Advisor advisor : advisors) {
           Map<Method, MethodHook> methodMethodHookMap = getAllMethodHook(advisor, target);
            for (Map.Entry<Method, MethodHook> methodListEntry : methodMethodHookMap.entrySet()) {
               if (methodListEntry.getValue() == null) {
                   continue;
               }
               methodListMap.putIfAbsent(methodListEntry.getKey(), new ArrayList<>());
               methodListMap.get(methodListEntry.getKey()).add(methodListEntry.getValue());
            }

        }
        return methodListMap;
    }
}
